package com.aeye.mbr.common.plugin.sequence;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Created by shengpeng on 2018/4/2.
 * 采用SnowFlake算法实现全局唯一自增id
 */
public class SnowFlakeSequenceGenerator implements  SequenceGenerator {
    private static Logger log = LoggerFactory.getLogger(SnowFlakeSequenceGenerator.class);
    private Map<String,Integer> dataCenters = new HashMap<>();
    public static final String DEFAULTPROPERTIES = "data/dataCenterId.properties";
    private IdWorker idWorker ;

    public SnowFlakeSequenceGenerator() {
        try {
           Properties properties = new Properties();
           properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULTPROPERTIES));
            for(Object key:properties.keySet())
            {
                dataCenters.put(key.toString().trim(), Integer.parseInt(properties.get(key).toString().trim()));
            }
        } catch (IOException e) {
            log.error("could not load dataCenterId.properties");
            System.exit(-1);
        }
    }

    @Override
    public long getSequence(String seqName) {
        if(null == idWorker){
            throw new RuntimeException("请先初始化IdWorker");
        }else{
            return idWorker.nextId();
        }
    }

    public void initIdWorker(String areaCode,int workerId) {
        Integer id = dataCenters.get(areaCode);
        if(null == id){
            log.error("could not find id of the data center with code {}",areaCode);
            throw new RuntimeException("无法初始化"+areaCode+"对应的数据中心, 请检查applicationContext-listener.xml配置文件中ApplicationListener的dataCenterCode属性是否正确配置");
        }else{
            if(id == 0){
                log.warn("请手动配置applicationContext-listener.xml配置文件中ApplicationListener的dataCenterCode属性，以确保SnowFlakeSequenceGenerator产生的ID是全局唯一的！");
            }
            idWorker = new IdWorker(workerId,id);
        }
    }

    public class IdWorker {
        private final long twepoch = 1288834974657L;
        private final long workerIdBits = 5L;
        private final long datacenterIdBits = 9L;
        private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
        private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
        private final long sequenceBits = 8L;
        private final long workerIdShift = sequenceBits;
        private final long datacenterIdShift = sequenceBits + workerIdBits;
        private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        private final long sequenceMask = -1L ^ (-1L << sequenceBits);

        private long workerId;
        private long datacenterId;
        private long sequence = 0L;
        private long lastTimestamp = -1L;

        public IdWorker(long workerId, long datacenterId) {
            if (workerId > maxWorkerId || workerId < 0) {
                throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
            }
            if (datacenterId > maxDatacenterId || datacenterId < 0) {
                throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
            }
            this.workerId = workerId;
            this.datacenterId = datacenterId;
        }

        public synchronized long nextId() {
            long timestamp = timeGen();
            if (timestamp < lastTimestamp) {
                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
            }
            if (lastTimestamp == timestamp) {
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }

            lastTimestamp = timestamp;

            return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
        }

        protected long tilNextMillis(long lastTimestamp) {
            long timestamp = timeGen();
            while (timestamp <= lastTimestamp) {
                timestamp = timeGen();
            }
            return timestamp;
        }

        protected long timeGen() {
            return System.currentTimeMillis();
        }
    }
}
